package org.simplespringframework.inject;

import lombok.extern.slf4j.Slf4j;
import org.simplespringframework.BeanContainer;
import org.simplespringframework.annotation.Component;
import org.simplespringframework.inject.annotation.Autowired;
import org.simplespringframework.util.ClassUtil;
import org.simplespringframework.util.ValidationUtil;

import java.lang.reflect.Field;
import java.util.Set;


@Slf4j
public class DependencyInjector {

    /*
    bean容器
     */
    private BeanContainer beanContainer;

    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 处理依赖注入
     */
    public void doIoc() {
        Set<Class<?>> beanClasses = beanContainer.getClasses();
        if (ValidationUtil.isEmpty(beanClasses)) {
            log.warn("empty beanClasses in BeanContainer , beanClasses.size():" + beanClasses.size());
            return;
        }
        // 1 遍历Bean容器中所有的class对象
        for (Class<?> beanClass : beanClasses) {
            // 2 遍历Class对象的所有成员变量
            Field[] fields = beanClass.getDeclaredFields();
            if (ValidationUtil.isEmpty(fields)) {
                continue;
            }
            for (Field field : fields) {
                // 3 找出被Autowired标记的成员变量
                if (field.isAnnotationPresent(Autowired.class)) {
                    // 获取Autowired 的value值
                    String autowiredValue = field.getAnnotation(Autowired.class).value();
                    // 4 获取这些成员变量的类型
                    Class<?> fieldClass = field.getType();  // 实例类型
                    // 5 获取这些成员变量的类型在容器里对应的实例
                    Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
                    if (fieldValue == null) {
                        // 注入失败
                        throw new RuntimeException("unable to inject relevant type, target fieldClass is:" + fieldClass.getName());
                    } else {
                        // 6 通过反射将对应的成员变量实例注入到成员变量所在类的实例里
                        Object targetBean = beanContainer.getBean(beanClass);
                        ClassUtil.setField(field,targetBean,fieldValue,true);
                    }
                }
            }
        }



    }

    /**
     * 根据class在beanContainer里获取其实例或实现类
     * @param fieldClass class
     * @param autowiredValue @Autowired注解的value值
     * @return
     */
    private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
        Object fieldValue = beanContainer.getBean(fieldClass);
        if (fieldValue != null) {
            return fieldValue;
        } else {
            Class<?> implementClass =  getImplementClass(fieldClass, autowiredValue);
            if (implementClass != null) {
                return beanContainer.getBean(implementClass);
            } else {
                return null;
            }
        }
    }

    /**
     * 获取接口的实现类
     * @param fieldClass fieldClass
     * @param autowiredValue @Autowired注解的value值
     * @return Class
     */
    private Class<?> getImplementClass(Class<?> fieldClass, String autowiredValue) {
        Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
        if (!ValidationUtil.isEmpty(classSet)) {
            if (ValidationUtil.isEmpty(autowiredValue)) {
                // Autowired 没有指定value值
                if (classSet.size() == 1) {
                    // 返回唯一的值
                    return classSet.iterator().next();
                } else {
                    // 如果多余两个实现类且用户未指定其中一个实现类，则抛出异常
                    throw new RuntimeException("multiple implement classes for " + fieldClass.getName() +
                            "please set @Autowired's value to pick one!");
                }
            } else {
                // value有值
                for (Class<?> clazz : classSet) {
                    // 获取某个实现类的类名 clazz.getSimpleName() 与 value比较
                    if (autowiredValue.equals(clazz.getSimpleName())) {
                        return clazz;
                    }
                }
            }
            return null;
        } else {
            return null;
        }
    }
}
